package com.hww.common.util;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

/**
 * ShellUtils 类
 * <ul>
 * <strong>检查Root权限</strong>
 * <li>{@link ShellUtils#checkRootPermission()} 方法用于检测设备是否具有root权限</li>
 * </ul>
 * <ul>
 * <strong>执行命令</strong>
 * 提供多个重载方法用于执行shell命令：
 * <li>{@link ShellUtils#execCommand(String, boolean)} 执行单个命令字符串</li>
 * <li>{@link ShellUtils#execCommand(String, boolean, boolean)} 执行单个命令并控制是否需要结果信息</li>
 * <li>{@link ShellUtils#execCommand(List, boolean)} 执行命令列表</li>
 * <li>{@link ShellUtils#execCommand(List, boolean, boolean)} 执行命令列表并控制是否需要结果信息</li>
 * <li>{@link ShellUtils#execCommand(String[], boolean)} 执行命令字符串数组</li>
 * <li>{@link ShellUtils#execCommand(String[], boolean, boolean)} 执行命令字符串数组并控制是否需要结果信息</li>
 * </ul>
 */
public class ShellUtils {

    /*屏幕截图命令*/
    public static final String SCREEN = "screencap -p /sdcard/screen.png";

    /* su 命令，用于请求root权限 */
    public static final String COMMAND_SU = "su";
    /* sh 命令，用于启动shell环境 */
    public static final String COMMAND_SH = "sh";
    /* 退出命令 */
    public static final String COMMAND_EXIT = "exit\n";
    /* 命令行结束符 */
    public static final String COMMAND_LINE_END = "\n";

    /**
     * 检测设备是否具有root权限
     *
     * @return true有root权限  false 没有root权限
     */
    public static boolean checkRootPermission() {
        return execCommand("echo root", true, false).result == 0;
    }

    /**
     * 执行单个shell命令，默认获取执行结果信息
     *
     * @param command 要执行的命令。
     * @param isRoot  是否以root权限执行。
     * @return CommandResult对象，包含命令执行结果代码、标准输出和错误输出。
     * @see ShellUtils#execCommand(String[], boolean, boolean)
     */
    public static CommandResult execCommand(String command, boolean isRoot) {
        return execCommand(new String[]{command}, isRoot, true);
    }

    /**
     * 执行命令列表，默认获取执行结果信息
     *
     * @param commands 要执行的命令数组。
     * @param isRoot   是否以root权限执行。
     * @return CommandResult对象，包含命令执行结果代码、标准输出和错误输出。
     * @see ShellUtils#execCommand(String[], boolean, boolean)
     */
    public static CommandResult execCommand(List<String> commands, boolean isRoot) {
        return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, true);
    }

    /**
     * 执行命令数组，默认获取执行结果信息
     *
     * @param commands 要执行的命令数组。
     * @param isRoot   是否以root权限执行。
     * @return CommandResult对象，包含命令执行结果代码、标准输出和错误输出。
     * @see ShellUtils#execCommand(String[], boolean, boolean)
     */
    public static CommandResult execCommand(String[] commands, boolean isRoot) {
        return execCommand(commands, isRoot, true);
    }

    /**
     * 执行单个shell命令，可控制是否获取执行结果信息
     *
     * @param command         要执行的命令。
     * @param isRoot          是否以root权限执行。
     * @param isNeedResultMsg 是否需要命令的执行结果。
     * @return CommandResult对象，包含命令执行结果代码、标准输出和错误输出。
     * @see ShellUtils#execCommand(String[], boolean, boolean)
     */
    public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {
        return execCommand(new String[]{command}, isRoot, isNeedResultMsg);
    }

    /**
     * 执行一系列命令，并根据需求返回执行结果。
     * <p>
     * 通过调用Runtime.getRuntime().exec()方法执行命令，支持以root权限执行。
     * 如果isNeedResultMsg为true，则会收集命令执行的标准输出和错误输出。
     *
     * @param commands        要执行的命令数组。
     * @param isRoot          是否以root权限执行。
     * @param isNeedResultMsg 是否需要命令的执行结果。
     * @return CommandResult对象，包含命令执行结果代码、标准输出和错误输出。
     * @see ShellUtils#execCommand(String[], boolean, boolean)
     */
    public static CommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg) {
        return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, isNeedResultMsg);
    }

    /**
     * 执行一系列命令，并根据需求返回执行结果。
     * <p>
     * 通过调用Runtime.getRuntime().exec()方法执行命令，支持以root权限执行。
     * 如果isNeedResultMsg为true，则会收集命令执行的标准输出和错误输出。
     *
     * @param commands        要执行的命令数组。
     * @param isRoot          是否以root权限执行。
     * @param isNeedResultMsg 是否需要命令的执行结果。
     * @return CommandResult对象，包含命令执行结果代码、标准输出和错误输出。
     */
    public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) {
        int result = -1;
        if (commands == null || commands.length == 0) {
            return new CommandResult(result, null, null);
        }

        /* 用于执行命令的进程。 */
        Process process = null;
        /* 用于读取命令执行成功输出的BufferedReader。 */
        BufferedReader successResult = null;
        /* 用于读取命令执行错误输出的BufferedReader。 */
        BufferedReader errorResult = null;
        /* 用于拼接命令执行成功输出的StringBuilder。 */
        StringBuilder successMsg = null;
        /* 用于拼接命令执行错误输出的StringBuilder。 */
        StringBuilder errorMsg = null;

        /* 用于向进程发送命令的DataOutputStream。 */
        DataOutputStream os = null;
        try {
            /* 根据是否以root权限执行，选择执行命令的shell */
            process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
            os = new DataOutputStream(process.getOutputStream());
            for (String command : commands) {
                if (command == null) {
                    continue;
                }

                // donnot use os.writeBytes(commmand), avoid chinese charset error
                /*将命令以字节形式写入输出流，避免字符集错误*/
                os.write(command.getBytes());
                /*在命令后添加换行符，表示命令结束*/
                os.writeBytes(COMMAND_LINE_END);
                /*刷新输出流，确保命令被立即发送*/
                os.flush();
            }
            /*发送退出命令，结束进程*/
            os.writeBytes(COMMAND_EXIT);
            os.flush();

            /*待进程执行完毕，获取进程的退出码*/
            result = process.waitFor();
            // get command result
            if (isNeedResultMsg) {
                successMsg = new StringBuilder();
                errorMsg = new StringBuilder();
                successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
                errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                String s;
                while ((s = successResult.readLine()) != null) {
                    successMsg.append(s);
                }
                while ((s = errorResult.readLine()) != null) {
                    errorMsg.append(s);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (process != null) {
                process.destroy();
            }
        }
        return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null
                : errorMsg.toString());
    }

    /**
     * 命令执行结果类
     * <ul>
     * <li>{@link CommandResult#result} 表示命令执行结果码，0表示正常，其他值表示错误</li>
     * <li>{@link CommandResult#successMsg} 表示命令执行成功的消息</li>
     * <li>{@link CommandResult#errorMsg} 表示命令执行错误的消息</li>
     * </ul>
     */
    public static class CommandResult {

        /**
         * 命令执行结果码
         **/
        public int result;
        /**
         * 成功消息
         **/
        public String successMsg;
        /**
         * 错误消息
         **/
        public String errorMsg;

        public CommandResult(int result) {
            this.result = result;
        }

        public CommandResult(int result, String successMsg, String errorMsg) {
            this.result = result;
            this.successMsg = successMsg;
            this.errorMsg = errorMsg;
        }

        @Override
        public String toString() {
            final StringBuffer sb = new StringBuffer("CommandResult{");
            sb.append("result=").append(result);
            sb.append(", successMsg='").append(successMsg).append('\'');
            sb.append(", errorMsg='").append(errorMsg).append('\'');
            sb.append('}');
            return sb.toString();
        }
    }
}